// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/loader/resource_loader.h"

#include <stddef.h>
#include <stdint.h>

#include <deque>
#include <memory>
#include <utility>
#include <vector>

#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/loader/redirect_to_file_resource_handler.h"
#include "content/browser/loader/resource_loader_delegate.h"
#include "content/browser/loader/test_resource_handler.h"
#include "content/public/browser/client_certificate_delegate.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/resource_response.h"
#include "content/public/common/resource_type.h"
#include "content/public/test/mock_resource_context.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_web_contents.h"
#include "ipc/ipc_message.h"
#include "net/base/chunked_upload_data_stream.h"
#include "net/base/io_buffer.h"
#include "net/base/mock_file_stream.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/cert/x509_certificate.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/network_quality_estimator_test_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_private_key.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/test_data_directory.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_filter.h"
#include "net/url_request/url_request_interceptor.h"
#include "net/url_request/url_request_job_factory.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_job.h"
#include "net/url_request/url_request_test_util.h"
#include "storage/browser/blob/shareable_file_reference.h"
#include "testing/gtest/include/gtest/gtest.h"

using storage::ShareableFileReference;

namespace content {
namespace {

    // Stub client certificate store that returns a preset list of certificates for
    // each request and records the arguments of the most recent request for later
    // inspection.
    class ClientCertStoreStub : public net::ClientCertStore {
    public:
        // Creates a new ClientCertStoreStub that returns |response| on query. It
        // saves the number of requests and most recently certificate authorities list
        // in |requested_authorities| and |request_count|, respectively. The caller is
        // responsible for ensuring those pointers outlive the ClientCertStoreStub.
        //
        // TODO(ppi): Make the stub independent from the internal representation of
        // SSLCertRequestInfo. For now it seems that we can neither save the
        // scoped_refptr<> (since it is never passed to us) nor copy the entire
        // CertificateRequestInfo (since there is no copy constructor).
        ClientCertStoreStub(const net::CertificateList& response,
            int* request_count,
            std::vector<std::string>* requested_authorities)
            : response_(response)
            , requested_authorities_(requested_authorities)
            , request_count_(request_count)
        {
            requested_authorities_->clear();
            *request_count_ = 0;
        }

        ~ClientCertStoreStub() override { }

        // net::ClientCertStore:
        void GetClientCerts(const net::SSLCertRequestInfo& cert_request_info,
            net::CertificateList* selected_certs,
            const base::Closure& callback) override
        {
            *requested_authorities_ = cert_request_info.cert_authorities;
            ++(*request_count_);

            *selected_certs = response_;
            callback.Run();
        }

    private:
        const net::CertificateList response_;
        std::vector<std::string>* requested_authorities_;
        int* request_count_;
    };

    // Client certificate store which destroys its resource loader before the
    // asynchronous GetClientCerts callback is called.
    class LoaderDestroyingCertStore : public net::ClientCertStore {
    public:
        // Creates a client certificate store which, when looked up, posts a task to
        // reset |loader| and then call the callback. The caller is responsible for
        // ensuring the pointers remain valid until the process is complete.
        LoaderDestroyingCertStore(std::unique_ptr<ResourceLoader>* loader,
            const base::Closure& on_loader_deleted_callback)
            : loader_(loader)
            , on_loader_deleted_callback_(on_loader_deleted_callback)
        {
        }

        // net::ClientCertStore:
        void GetClientCerts(const net::SSLCertRequestInfo& cert_request_info,
            net::CertificateList* selected_certs,
            const base::Closure& cert_selected_callback) override
        {
            // Don't destroy |loader_| while it's on the stack.
            base::ThreadTaskRunnerHandle::Get()->PostTask(
                FROM_HERE, base::Bind(&LoaderDestroyingCertStore::DoCallback, base::Unretained(loader_), cert_selected_callback, on_loader_deleted_callback_));
        }

    private:
        // This needs to be static because |loader| owns the
        // LoaderDestroyingCertStore (ClientCertStores are actually handles, and not
        // global cert stores).
        static void DoCallback(std::unique_ptr<ResourceLoader>* loader,
            const base::Closure& cert_selected_callback,
            const base::Closure& on_loader_deleted_callback)
        {
            loader->reset();
            cert_selected_callback.Run();
            on_loader_deleted_callback.Run();
        }

        std::unique_ptr<ResourceLoader>* loader_;
        base::Closure on_loader_deleted_callback_;

        DISALLOW_COPY_AND_ASSIGN(LoaderDestroyingCertStore);
    };

    // A mock URLRequestJob which simulates an SSL client auth request.
    class MockClientCertURLRequestJob : public net::URLRequestTestJob {
    public:
        MockClientCertURLRequestJob(net::URLRequest* request,
            net::NetworkDelegate* network_delegate)
            : net::URLRequestTestJob(request, network_delegate)
            , weak_factory_(this)
        {
        }

        static std::vector<std::string> test_authorities()
        {
            return std::vector<std::string>(1, "dummy");
        }

        // net::URLRequestTestJob:
        void Start() override
        {
            scoped_refptr<net::SSLCertRequestInfo> cert_request_info(
                new net::SSLCertRequestInfo);
            cert_request_info->cert_authorities = test_authorities();
            base::ThreadTaskRunnerHandle::Get()->PostTask(
                FROM_HERE,
                base::Bind(&MockClientCertURLRequestJob::NotifyCertificateRequested,
                    weak_factory_.GetWeakPtr(),
                    base::RetainedRef(cert_request_info)));
        }

        void ContinueWithCertificate(net::X509Certificate* cert,
            net::SSLPrivateKey* private_key) override
        {
            net::URLRequestTestJob::Start();
        }

    private:
        ~MockClientCertURLRequestJob() override { }

        base::WeakPtrFactory<MockClientCertURLRequestJob> weak_factory_;

        DISALLOW_COPY_AND_ASSIGN(MockClientCertURLRequestJob);
    };

    class MockClientCertJobProtocolHandler
        : public net::URLRequestJobFactory::ProtocolHandler {
    public:
        // URLRequestJobFactory::ProtocolHandler implementation:
        net::URLRequestJob* MaybeCreateJob(
            net::URLRequest* request,
            net::NetworkDelegate* network_delegate) const override
        {
            return new MockClientCertURLRequestJob(request, network_delegate);
        }
    };

    // Set up dummy values to use in test HTTPS requests.

    scoped_refptr<net::X509Certificate> GetTestCert()
    {
        return net::ImportCertFromFile(net::GetTestCertsDirectory(),
            "test_mail_google_com.pem");
    }

    const net::CertStatus kTestCertError = net::CERT_STATUS_DATE_INVALID;
    const int kTestSecurityBits = 256;
    // SSL3 TLS_DHE_RSA_WITH_AES_256_CBC_SHA
    const int kTestConnectionStatus = 0x300039;

    // A mock URLRequestJob which simulates an HTTPS request.
    class MockHTTPSURLRequestJob : public net::URLRequestTestJob {
    public:
        MockHTTPSURLRequestJob(net::URLRequest* request,
            net::NetworkDelegate* network_delegate,
            const std::string& response_headers,
            const std::string& response_data,
            bool auto_advance)
            : net::URLRequestTestJob(request,
                network_delegate,
                response_headers,
                response_data,
                auto_advance)
        {
        }

        // net::URLRequestTestJob:
        void GetResponseInfo(net::HttpResponseInfo* info) override
        {
            // Get the original response info, but override the SSL info.
            net::URLRequestJob::GetResponseInfo(info);
            info->ssl_info.cert = GetTestCert();
            info->ssl_info.cert_status = kTestCertError;
            info->ssl_info.security_bits = kTestSecurityBits;
            info->ssl_info.connection_status = kTestConnectionStatus;
        }

    private:
        ~MockHTTPSURLRequestJob() override { }

        DISALLOW_COPY_AND_ASSIGN(MockHTTPSURLRequestJob);
    };

    const char kRedirectHeaders[] = "HTTP/1.1 302 Found\n"
                                    "Location: https://example.test\n"
                                    "\n";

    class MockHTTPSJobURLRequestInterceptor : public net::URLRequestInterceptor {
    public:
        MockHTTPSJobURLRequestInterceptor(bool redirect)
            : redirect_(redirect)
        {
        }
        ~MockHTTPSJobURLRequestInterceptor() override { }

        // net::URLRequestInterceptor:
        net::URLRequestJob* MaybeInterceptRequest(
            net::URLRequest* request,
            net::NetworkDelegate* network_delegate) const override
        {
            std::string headers = redirect_ ? std::string(kRedirectHeaders, arraysize(kRedirectHeaders))
                                            : net::URLRequestTestJob::test_headers();
            return new MockHTTPSURLRequestJob(request, network_delegate, headers,
                "dummy response", true);
        }

    private:
        bool redirect_;
    };

    // Arbitrary read buffer size.
    // Test browser client that captures calls to SelectClientCertificates and
    // records the arguments of the most recent call for later inspection.
    class SelectCertificateBrowserClient : public TestContentBrowserClient {
    public:
        SelectCertificateBrowserClient()
            : call_count_(0)
        {
        }

        // Waits until the first call to SelectClientCertificate.
        void WaitForSelectCertificate()
        {
            select_certificate_run_loop_.Run();
            // Process any pending messages - just so tests can check if
            // SelectClientCertificate was called more than once.
            base::RunLoop().RunUntilIdle();
        }

        void SelectClientCertificate(
            WebContents* web_contents,
            net::SSLCertRequestInfo* cert_request_info,
            std::unique_ptr<ClientCertificateDelegate> delegate) override
        {
            EXPECT_FALSE(delegate_.get());

            ++call_count_;
            passed_certs_ = cert_request_info->client_certs;
            delegate_ = std::move(delegate);
            select_certificate_run_loop_.Quit();
        }

        int call_count() { return call_count_; }
        net::CertificateList passed_certs() { return passed_certs_; }

        void ContinueWithCertificate(net::X509Certificate* cert)
        {
            delegate_->ContinueWithCertificate(cert);
            delegate_.reset();
        }

        void CancelCertificateSelection() { delegate_.reset(); }

    private:
        net::CertificateList passed_certs_;
        int call_count_;
        std::unique_ptr<ClientCertificateDelegate> delegate_;

        base::RunLoop select_certificate_run_loop_;

        DISALLOW_COPY_AND_ASSIGN(SelectCertificateBrowserClient);
    };

    // Wraps a ChunkedUploadDataStream to behave as non-chunked to enable upload
    // progress reporting.
    class NonChunkedUploadDataStream : public net::UploadDataStream {
    public:
        explicit NonChunkedUploadDataStream(uint64_t size)
            : net::UploadDataStream(false, 0)
            , stream_(0)
            , size_(size)
        {
        }

        void AppendData(const char* data)
        {
            stream_.AppendData(data, strlen(data), false);
        }

    private:
        int InitInternal(const net::NetLogWithSource& net_log) override
        {
            SetSize(size_);
            stream_.Init(base::Bind(&NonChunkedUploadDataStream::OnInitCompleted,
                             base::Unretained(this)),
                net_log);
            return net::OK;
        }

        int ReadInternal(net::IOBuffer* buf, int buf_len) override
        {
            return stream_.Read(buf, buf_len,
                base::Bind(&NonChunkedUploadDataStream::OnReadCompleted,
                    base::Unretained(this)));
        }

        void ResetInternal() override { stream_.Reset(); }

        net::ChunkedUploadDataStream stream_;
        uint64_t size_;

        DISALLOW_COPY_AND_ASSIGN(NonChunkedUploadDataStream);
    };

    // Fails to create a temporary file with the given error.
    void CreateTemporaryError(
        base::File::Error error,
        const CreateTemporaryFileStreamCallback& callback)
    {
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE,
            base::Bind(callback, error,
                base::Passed(std::unique_ptr<net::FileStream>()), nullptr));
    }

} // namespace

class ResourceLoaderTest : public testing::Test,
                           public ResourceLoaderDelegate {
protected:
    ResourceLoaderTest()
        : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP)
        , test_url_request_context_(true)
        , resource_context_(&test_url_request_context_)
        , raw_ptr_resource_handler_(NULL)
        , raw_ptr_to_request_(NULL)
    {
        test_url_request_context_.set_job_factory(&job_factory_);
        test_url_request_context_.set_network_quality_estimator(
            &network_quality_estimator_);
        test_url_request_context_.Init();
    }

    // URL with a response body of test_data() where reads complete synchronously.
    GURL test_sync_url() const { return net::URLRequestTestJob::test_url_1(); }

    // URL with a response body of test_data() where reads complete
    // asynchronously.
    GURL test_async_url() const
    {
        return net::URLRequestTestJob::test_url_auto_advance_async_reads_1();
    }

    // URL that redirects to test_sync_url(). The ResourceLoader is set up to
    // use this URL by default.
    GURL test_redirect_url() const
    {
        return net::URLRequestTestJob::test_url_redirect_to_url_1();
    }

    std::string test_data() const
    {
        return net::URLRequestTestJob::test_data_1();
    }

    net::TestNetworkQualityEstimator* network_quality_estimator()
    {
        return &network_quality_estimator_;
    }

    virtual std::unique_ptr<net::URLRequestJobFactory::ProtocolHandler>
    CreateProtocolHandler()
    {
        return net::URLRequestTestJob::CreateProtocolHandler();
    }

    virtual std::unique_ptr<ResourceHandler> WrapResourceHandler(
        std::unique_ptr<TestResourceHandler> leaf_handler,
        net::URLRequest* request)
    {
        return std::move(leaf_handler);
    }

    // Replaces loader_ with a new one for |request|.
    void SetUpResourceLoader(std::unique_ptr<net::URLRequest> request,
        ResourceType resource_type,
        bool belongs_to_main_frame)
    {
        raw_ptr_to_request_ = request.get();

        // A request marked as a main frame request must also belong to a main
        // frame.
        ASSERT_TRUE((resource_type != RESOURCE_TYPE_MAIN_FRAME) || belongs_to_main_frame);

        RenderFrameHost* rfh = web_contents_->GetMainFrame();
        ResourceRequestInfo::AllocateForTesting(
            request.get(), resource_type, &resource_context_,
            rfh->GetProcess()->GetID(), rfh->GetRenderViewHost()->GetRoutingID(),
            rfh->GetRoutingID(), belongs_to_main_frame,
            false /* parent_is_main_frame */, true /* allow_download */,
            false /* is_async */, PREVIEWS_OFF /* previews_state */);
        std::unique_ptr<TestResourceHandler> resource_handler(
            new TestResourceHandler(nullptr, nullptr));
        raw_ptr_resource_handler_ = resource_handler.get();
        loader_.reset(new ResourceLoader(
            std::move(request),
            WrapResourceHandler(std::move(resource_handler), raw_ptr_to_request_),
            this));
    }

    void SetUpResourceLoaderForUrl(const GURL& test_url)
    {
        std::unique_ptr<net::URLRequest> request(
            resource_context_.GetRequestContext()->CreateRequest(
                test_url, net::DEFAULT_PRIORITY, nullptr /* delegate */));
        SetUpResourceLoader(std::move(request), RESOURCE_TYPE_MAIN_FRAME, true);
    }

    void SetUp() override
    {
        job_factory_.SetProtocolHandler("test", CreateProtocolHandler());
        net::URLRequestFailedJob::AddUrlHandler();

        browser_context_.reset(new TestBrowserContext());
        scoped_refptr<SiteInstance> site_instance = SiteInstance::Create(browser_context_.get());
        web_contents_.reset(
            TestWebContents::Create(browser_context_.get(), site_instance.get()));
        SetUpResourceLoaderForUrl(test_redirect_url());
    }

    void TearDown() override
    {
        // Destroy the WebContents and pump the event loop before destroying
        // |rvh_test_enabler_| and |thread_bundle_|. This lets asynchronous cleanup
        // tasks complete.
        web_contents_.reset();

        // Clean up handlers.
        net::URLRequestFilter::GetInstance()->ClearHandlers();

        base::RunLoop().RunUntilIdle();
    }

    void SetClientCertStore(std::unique_ptr<net::ClientCertStore> store)
    {
        dummy_cert_store_ = std::move(store);
    }

    // ResourceLoaderDelegate:
    ResourceDispatcherHostLoginDelegate* CreateLoginDelegate(
        ResourceLoader* loader,
        net::AuthChallengeInfo* auth_info) override
    {
        return NULL;
    }
    bool HandleExternalProtocol(ResourceLoader* loader,
        const GURL& url) override
    {
        EXPECT_EQ(loader, loader_.get());
        ++handle_external_protocol_;

        // Check that calls to HandleExternalProtocol always happen after the calls
        // to the ResourceHandler's OnWillStart and OnRequestRedirected.
        EXPECT_EQ(handle_external_protocol_,
            raw_ptr_resource_handler_->on_will_start_called() + raw_ptr_resource_handler_->on_request_redirected_called());

        bool return_value = handle_external_protocol_results_.front();
        if (handle_external_protocol_results_.size() > 1)
            handle_external_protocol_results_.pop_front();
        return return_value;
    }
    void DidStartRequest(ResourceLoader* loader) override
    {
        EXPECT_EQ(loader, loader_.get());
        EXPECT_EQ(0, did_finish_loading_);
        EXPECT_EQ(0, did_start_request_);
        ++did_start_request_;
    }
    void DidReceiveRedirect(ResourceLoader* loader,
        const GURL& new_url,
        ResourceResponse* response) override
    {
        EXPECT_EQ(loader, loader_.get());
        EXPECT_EQ(0, did_finish_loading_);
        EXPECT_EQ(0, did_receive_response_);
        EXPECT_EQ(1, did_start_request_);
        ++did_received_redirect_;
    }
    void DidReceiveResponse(ResourceLoader* loader) override
    {
        EXPECT_EQ(loader, loader_.get());
        EXPECT_EQ(0, did_finish_loading_);
        EXPECT_EQ(0, did_receive_response_);
        EXPECT_EQ(1, did_start_request_);
        ++did_receive_response_;
    }
    void DidFinishLoading(ResourceLoader* loader) override
    {
        EXPECT_EQ(loader, loader_.get());
        EXPECT_EQ(0, did_finish_loading_);

        // Shouldn't be in a recursive ResourceHandler call - this is normally where
        // the ResourceLoader (And thus the ResourceHandler chain) is destroyed.
        EXPECT_EQ(0, raw_ptr_resource_handler_->call_depth());

        ++did_finish_loading_;
    }
    std::unique_ptr<net::ClientCertStore> CreateClientCertStore(
        ResourceLoader* loader) override
    {
        return std::move(dummy_cert_store_);
    }

    TestBrowserThreadBundle thread_bundle_;
    RenderViewHostTestEnabler rvh_test_enabler_;

    // Record which ResourceDispatcherHostDelegate methods have been invoked.
    int did_start_request_ = 0;
    int did_received_redirect_ = 0;
    int did_receive_response_ = 0;
    int did_finish_loading_ = 0;
    int handle_external_protocol_ = 0;

    // Allows controlling the return values of sequential calls to
    // HandleExternalProtocol. Values are removed by the measure they are used
    // but the last one which is used for all following calls.
    std::deque<bool> handle_external_protocol_results_ { false };

    net::URLRequestJobFactoryImpl job_factory_;
    net::TestNetworkQualityEstimator network_quality_estimator_;
    net::TestURLRequestContext test_url_request_context_;
    MockResourceContext resource_context_;
    std::unique_ptr<TestBrowserContext> browser_context_;
    std::unique_ptr<TestWebContents> web_contents_;
    std::unique_ptr<net::ClientCertStore> dummy_cert_store_;

    // The ResourceLoader owns the URLRequest and the ResourceHandler.
    TestResourceHandler* raw_ptr_resource_handler_;
    net::URLRequest* raw_ptr_to_request_;
    std::unique_ptr<ResourceLoader> loader_;
};

class ClientCertResourceLoaderTest : public ResourceLoaderTest {
protected:
    std::unique_ptr<net::URLRequestJobFactory::ProtocolHandler>
    CreateProtocolHandler() override
    {
        return base::WrapUnique(new MockClientCertJobProtocolHandler);
    }

    void SetUp() override
    {
        ResourceLoaderTest::SetUp();
        // These tests don't expect any redirects.
        SetUpResourceLoaderForUrl(test_sync_url());
    }
};

// A ResourceLoaderTest that intercepts https://example.test and
// https://example-redirect.test URLs and sets SSL info on the
// responses. The latter serves a Location: header in the response.
class HTTPSSecurityInfoResourceLoaderTest : public ResourceLoaderTest {
public:
    HTTPSSecurityInfoResourceLoaderTest()
        : ResourceLoaderTest()
        , test_https_url_("https://example.test")
        , test_https_redirect_url_("https://example-redirect.test")
    {
    }

    ~HTTPSSecurityInfoResourceLoaderTest() override { }

    const GURL& test_https_url() const { return test_https_url_; }
    const GURL& test_https_redirect_url() const
    {
        return test_https_redirect_url_;
    }

protected:
    void SetUp() override
    {
        ResourceLoaderTest::SetUp();
        net::URLRequestFilter::GetInstance()->ClearHandlers();
        net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
            "https", "example.test",
            std::unique_ptr<net::URLRequestInterceptor>(
                new MockHTTPSJobURLRequestInterceptor(false /* redirect */)));
        net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
            "https", "example-redirect.test",
            std::unique_ptr<net::URLRequestInterceptor>(
                new MockHTTPSJobURLRequestInterceptor(true /* redirect */)));
    }

private:
    const GURL test_https_url_;
    const GURL test_https_redirect_url_;
};

// Tests that client certificates are requested with ClientCertStore lookup.
TEST_F(ClientCertResourceLoaderTest, WithStoreLookup)
{
    // Set up the test client cert store.
    int store_request_count;
    std::vector<std::string> store_requested_authorities;
    net::CertificateList dummy_certs(1, GetTestCert());
    std::unique_ptr<ClientCertStoreStub> test_store(new ClientCertStoreStub(
        dummy_certs, &store_request_count, &store_requested_authorities));
    SetClientCertStore(std::move(test_store));

    // Plug in test content browser client.
    SelectCertificateBrowserClient test_client;
    ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client);

    // Start the request and wait for it to pause.
    loader_->StartRequest();
    test_client.WaitForSelectCertificate();

    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());

    // Check if the test store was queried against correct |cert_authorities|.
    EXPECT_EQ(1, store_request_count);
    EXPECT_EQ(MockClientCertURLRequestJob::test_authorities(),
        store_requested_authorities);

    // Check if the retrieved certificates were passed to the content browser
    // client.
    EXPECT_EQ(1, test_client.call_count());
    EXPECT_EQ(dummy_certs, test_client.passed_certs());

    // Continue the request.
    test_client.ContinueWithCertificate(nullptr);
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(net::OK, raw_ptr_resource_handler_->final_status().error());

    // Restore the original content browser client.
    SetBrowserClientForTesting(old_client);
}

// Tests that client certificates are requested on a platform with NULL
// ClientCertStore.
TEST_F(ClientCertResourceLoaderTest, WithNullStore)
{
    // Plug in test content browser client.
    SelectCertificateBrowserClient test_client;
    ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client);

    // Start the request and wait for it to pause.
    loader_->StartRequest();
    test_client.WaitForSelectCertificate();

    // Check if the SelectClientCertificate was called on the content browser
    // client.
    EXPECT_EQ(1, test_client.call_count());
    EXPECT_EQ(net::CertificateList(), test_client.passed_certs());

    // Continue the request.
    test_client.ContinueWithCertificate(nullptr);
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(net::OK, raw_ptr_resource_handler_->final_status().error());

    // Restore the original content browser client.
    SetBrowserClientForTesting(old_client);
}

// Tests that the ContentBrowserClient may cancel a certificate request.
TEST_F(ClientCertResourceLoaderTest, CancelSelection)
{
    // Plug in test content browser client.
    SelectCertificateBrowserClient test_client;
    ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client);

    // Start the request and wait for it to pause.
    loader_->StartRequest();
    test_client.WaitForSelectCertificate();

    // Check if the SelectClientCertificate was called on the content browser
    // client.
    EXPECT_EQ(1, test_client.call_count());
    EXPECT_EQ(net::CertificateList(), test_client.passed_certs());

    // Cancel the request.
    test_client.CancelCertificateSelection();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED,
        raw_ptr_resource_handler_->final_status().error());

    // Restore the original content browser client.
    SetBrowserClientForTesting(old_client);
}

// Verifies that requests without WebContents attached abort.
TEST_F(ClientCertResourceLoaderTest, NoWebContents)
{
    // Destroy the WebContents before starting the request.
    web_contents_.reset();

    // Plug in test content browser client.
    SelectCertificateBrowserClient test_client;
    ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client);

    // Start the request and wait for it to complete.
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();

    // Check that SelectClientCertificate wasn't called and the request aborted.
    EXPECT_EQ(0, test_client.call_count());
    EXPECT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED,
        raw_ptr_resource_handler_->final_status().error());

    // Restore the original content browser client.
    SetBrowserClientForTesting(old_client);
}

// Verifies that ClientCertStore's callback doesn't crash if called after the
// loader is destroyed.
TEST_F(ClientCertResourceLoaderTest, StoreAsyncCancel)
{
    base::RunLoop loader_destroyed_run_loop;
    LoaderDestroyingCertStore* test_store = new LoaderDestroyingCertStore(&loader_,
        loader_destroyed_run_loop.QuitClosure());
    SetClientCertStore(base::WrapUnique(test_store));

    loader_->StartRequest();
    loader_destroyed_run_loop.Run();
    EXPECT_FALSE(loader_);

    // Pump the event loop to ensure nothing asynchronous crashes either.
    base::RunLoop().RunUntilIdle();
}

// Test the case the ResourceHandler defers nothing.
TEST_F(ResourceLoaderTest, SyncResourceHandler)
{
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(1, did_start_request_);
    EXPECT_EQ(1, did_received_redirect_);
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(2, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::OK, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());
}

// Same as above, except reads complete asynchronously, and there's no redirect.
TEST_F(ResourceLoaderTest, SyncResourceHandlerAsyncReads)
{
    SetUpResourceLoaderForUrl(test_async_url());

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(1, did_start_request_);
    EXPECT_EQ(0, did_received_redirect_);
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::OK, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());
}

// Test the case where ResourceHandler defers nothing and the request is handled
// as an external protocol on start.
TEST_F(ResourceLoaderTest, SyncExternalProtocolHandlingOnStart)
{
    handle_external_protocol_results_ = { true };

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(0, did_start_request_);
    EXPECT_EQ(0, did_received_redirect_);
    EXPECT_EQ(0, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_TRUE(raw_ptr_resource_handler_->body().empty());
}

// Test the case where ResourceHandler defers nothing and the request is handled
// as an external protocol on redirect.
TEST_F(ResourceLoaderTest, SyncExternalProtocolHandlingOnRedirect)
{
    handle_external_protocol_results_ = { false, true };

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(1, did_start_request_);
    EXPECT_EQ(1, did_received_redirect_);
    EXPECT_EQ(0, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(2, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_TRUE(raw_ptr_resource_handler_->body().empty());
}

// Test the case the ResourceHandler defers everything.
TEST_F(ResourceLoaderTest, AsyncResourceHandler)
{
    raw_ptr_resource_handler_->set_defer_on_will_start(true);
    raw_ptr_resource_handler_->set_defer_on_request_redirected(true);
    raw_ptr_resource_handler_->set_defer_on_response_started(true);
    raw_ptr_resource_handler_->set_defer_on_read_completed(true);
    raw_ptr_resource_handler_->set_defer_on_read_eof(true);
    raw_ptr_resource_handler_->set_defer_on_response_completed(true);

    // Start and run until OnWillStart.
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, did_start_request_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(0, handle_external_protocol_);

    // Resume and run until OnRequestRedirected.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, handle_external_protocol_);

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_received_redirect_);
    EXPECT_EQ(0, did_receive_response_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(1, handle_external_protocol_);

    // Resume and run until OnResponseStarted.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(2, handle_external_protocol_);

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(0, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());

    // Resume and run until OnReadCompleted.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_completed_called());

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());

    // Resume and run until the final 0-byte read, signaling EOF.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_eof());

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());

    // Resume and run until OnResponseCompleted is called, which again defers the
    // request.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(0, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::OK, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());

    // Resume and run until all pending tasks. Note that OnResponseCompleted was
    // invoked in the previous section, so can't use RunUntilCompleted().
    raw_ptr_resource_handler_->Resume();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::OK, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());
    EXPECT_EQ(2, handle_external_protocol_);
}

// Same as above, except reads complete asynchronously and there's no redirect.
TEST_F(ResourceLoaderTest, AsyncResourceHandlerAsyncReads)
{
    SetUpResourceLoaderForUrl(test_async_url());

    raw_ptr_resource_handler_->set_defer_on_will_start(true);
    raw_ptr_resource_handler_->set_defer_on_response_started(true);
    raw_ptr_resource_handler_->set_defer_on_read_completed(true);
    raw_ptr_resource_handler_->set_defer_on_read_eof(true);
    raw_ptr_resource_handler_->set_defer_on_response_completed(true);

    // Start and run until OnWillStart.
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, did_start_request_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(0, handle_external_protocol_);

    // Resume and run until OnResponseStarted.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(1, handle_external_protocol_);

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(0, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());

    // Resume and run until OnReadCompleted.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_completed_called());

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());

    // Resume and run until the final 0-byte read, signalling EOF.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_eof());

    // Spinning the message loop should not advance the state further.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());

    // Resume and run until OnResponseCompleted is called, which again defers the
    // request.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(0, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::OK, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());

    // Resume and run until all pending tasks. Note that OnResponseCompleted was
    // invoked in the previous section, so can't use RunUntilCompleted().
    raw_ptr_resource_handler_->Resume();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::OK, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());
    EXPECT_EQ(1, handle_external_protocol_);
}

TEST_F(ResourceLoaderTest, SyncCancelOnWillStart)
{
    raw_ptr_resource_handler_->set_on_will_start_result(false);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, did_start_request_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(0, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, SyncCancelOnRequestRedirected)
{
    raw_ptr_resource_handler_->set_on_request_redirected_result(false);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_received_redirect_);
    EXPECT_EQ(0, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, SyncCancelOnResponseStarted)
{
    raw_ptr_resource_handler_->set_on_response_started_result(false);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, SyncCancelOnWillRead)
{
    raw_ptr_resource_handler_->set_on_will_read_result(false);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, SyncCancelOnReadCompleted)
{
    raw_ptr_resource_handler_->set_on_read_completed_result(false);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_LT(0u, raw_ptr_resource_handler_->body().size());
}

TEST_F(ResourceLoaderTest, SyncCancelOnReceivedEof)
{
    raw_ptr_resource_handler_->set_on_read_eof_result(false);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, SyncCancelOnAsyncReadCompleted)
{
    SetUpResourceLoaderForUrl(test_async_url());
    raw_ptr_resource_handler_->set_on_read_completed_result(false);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_LT(0u, raw_ptr_resource_handler_->body().size());
}

TEST_F(ResourceLoaderTest, SyncCancelOnAsyncReceivedEof)
{
    SetUpResourceLoaderForUrl(test_async_url());
    raw_ptr_resource_handler_->set_on_read_eof_result(false);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, AsyncCancelOnWillStart)
{
    raw_ptr_resource_handler_->set_defer_on_will_start(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, did_start_request_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(0, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, AsyncCancelOnRequestRedirected)
{
    raw_ptr_resource_handler_->set_defer_on_request_redirected(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_received_redirect_);
    EXPECT_EQ(0, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, AsyncCancelOnResponseStarted)
{
    raw_ptr_resource_handler_->set_defer_on_response_started(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_will_read_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, AsyncCancelOnReadCompleted)
{
    raw_ptr_resource_handler_->set_defer_on_read_completed(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_LT(0u, raw_ptr_resource_handler_->body().size());
}

TEST_F(ResourceLoaderTest, AsyncCancelOnReceivedEof)
{
    raw_ptr_resource_handler_->set_defer_on_read_eof(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, AsyncCancelOnAsyncReadCompleted)
{
    SetUpResourceLoaderForUrl(test_async_url());
    raw_ptr_resource_handler_->set_defer_on_read_completed(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_LT(0u, raw_ptr_resource_handler_->body().size());
}

TEST_F(ResourceLoaderTest, AsyncCancelOnAsyncReceivedEof)
{
    SetUpResourceLoaderForUrl(test_async_url());
    raw_ptr_resource_handler_->set_defer_on_read_eof(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_read_eof());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ(test_data(), raw_ptr_resource_handler_->body());
}

// Tests the request being deferred and then being handled as an external
// protocol, both on start.
TEST_F(ResourceLoaderTest, AsyncExternalProtocolHandlingOnStart)
{
    handle_external_protocol_results_ = { true };
    raw_ptr_resource_handler_->set_defer_on_will_start(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(0, did_finish_loading_);
    EXPECT_EQ(0, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());

    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(0, did_start_request_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

// Tests the request being deferred and then being handled as an external
// protocol, both on redirect.
TEST_F(ResourceLoaderTest, AsyncExternalProtocolHandlingOnRedirect)
{
    handle_external_protocol_results_ = { false, true };
    raw_ptr_resource_handler_->set_defer_on_request_redirected(true);

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();
    EXPECT_EQ(1, did_start_request_);
    EXPECT_EQ(1, did_received_redirect_);
    EXPECT_EQ(0, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());

    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(1, did_start_request_);
    EXPECT_EQ(1, did_received_redirect_);
    EXPECT_EQ(0, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(2, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());

    EXPECT_EQ(net::ERR_ABORTED,
        raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, RequestFailsOnStart)
{
    SetUpResourceLoaderForUrl(
        net::URLRequestFailedJob::GetMockHttpUrlWithFailurePhase(
            net::URLRequestFailedJob::START, net::ERR_FAILED));

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, did_received_redirect_);
    EXPECT_EQ(0, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, RequestFailsOnReadSync)
{
    SetUpResourceLoaderForUrl(
        net::URLRequestFailedJob::GetMockHttpUrlWithFailurePhase(
            net::URLRequestFailedJob::READ_SYNC, net::ERR_FAILED));

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, did_received_redirect_);
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, RequestFailsOnReadAsync)
{
    SetUpResourceLoaderForUrl(
        net::URLRequestFailedJob::GetMockHttpUrlWithFailurePhase(
            net::URLRequestFailedJob::READ_ASYNC, net::ERR_FAILED));

    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(0, did_received_redirect_);
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, OutOfBandCancelDuringStart)
{
    SetUpResourceLoaderForUrl(
        net::URLRequestFailedJob::GetMockHttpUrlWithFailurePhase(
            net::URLRequestFailedJob::START, net::ERR_IO_PENDING));

    loader_->StartRequest();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(1, handle_external_protocol_);

    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    raw_ptr_resource_handler_->WaitUntilResponseComplete();

    EXPECT_EQ(0, did_received_redirect_);
    EXPECT_EQ(0, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, OutOfBandCancelDuringRead)
{
    SetUpResourceLoaderForUrl(
        net::URLRequestFailedJob::GetMockHttpUrlWithFailurePhase(
            net::URLRequestFailedJob::READ_SYNC, net::ERR_IO_PENDING));

    loader_->StartRequest();
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(1, handle_external_protocol_);

    raw_ptr_resource_handler_->CancelWithError(net::ERR_FAILED);
    raw_ptr_resource_handler_->WaitUntilResponseComplete();
    EXPECT_EQ(0, did_received_redirect_);
    EXPECT_EQ(1, did_receive_response_);
    EXPECT_EQ(1, did_finish_loading_);
    EXPECT_EQ(1, handle_external_protocol_);
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_will_start_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_request_redirected_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_started_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_read_completed_called());
    EXPECT_EQ(1, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(net::ERR_FAILED, raw_ptr_resource_handler_->final_status().error());
    EXPECT_EQ("", raw_ptr_resource_handler_->body());
}

TEST_F(ResourceLoaderTest, ResumeCanceledRequest)
{
    raw_ptr_resource_handler_->set_defer_on_will_start(true);

    loader_->StartRequest();
    loader_->CancelRequest(true);
    static_cast<ResourceController*>(loader_.get())->Resume();
}

class ResourceLoaderRedirectToFileTest : public ResourceLoaderTest {
public:
    ResourceLoaderRedirectToFileTest()
        : file_stream_(NULL)
        , redirect_to_file_resource_handler_(NULL)
    {
    }

    ~ResourceLoaderRedirectToFileTest() override
    {
        // Releasing the loader should result in destroying the file asynchronously.
        file_stream_ = nullptr;
        deletable_file_ = nullptr;
        loader_.reset();

        // Wait for the task to delete the file to run, and make sure the file is
        // cleaned up.
        base::RunLoop().RunUntilIdle();
        EXPECT_FALSE(base::PathExists(temp_path()));
    }

    base::FilePath temp_path() const { return temp_path_; }
    ShareableFileReference* deletable_file() const
    {
        return deletable_file_.get();
    }
    net::testing::MockFileStream* file_stream() const { return file_stream_; }
    RedirectToFileResourceHandler* redirect_to_file_resource_handler() const
    {
        return redirect_to_file_resource_handler_;
    }

    std::unique_ptr<ResourceHandler> WrapResourceHandler(
        std::unique_ptr<TestResourceHandler> leaf_handler,
        net::URLRequest* request) override
    {
        leaf_handler->set_expect_on_data_downloaded(true);

        // Make a temporary file.
        CHECK(base::CreateTemporaryFile(&temp_path_));
        int flags = base::File::FLAG_WRITE | base::File::FLAG_TEMPORARY | base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_ASYNC;
        base::File file(temp_path_, flags);
        CHECK(file.IsValid());

        // Create mock file streams and a ShareableFileReference.
        std::unique_ptr<net::testing::MockFileStream> file_stream(
            new net::testing::MockFileStream(std::move(file),
                base::ThreadTaskRunnerHandle::Get()));
        file_stream_ = file_stream.get();
        deletable_file_ = ShareableFileReference::GetOrCreate(
            temp_path_, ShareableFileReference::DELETE_ON_FINAL_RELEASE,
            BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE).get());

        // Inject them into the handler.
        std::unique_ptr<RedirectToFileResourceHandler> handler(
            new RedirectToFileResourceHandler(std::move(leaf_handler), request));
        redirect_to_file_resource_handler_ = handler.get();
        handler->SetCreateTemporaryFileStreamFunctionForTesting(
            base::Bind(&ResourceLoaderRedirectToFileTest::PostCallback,
                base::Unretained(this),
                base::Passed(&file_stream)));
        return std::move(handler);
    }

private:
    void PostCallback(std::unique_ptr<net::FileStream> file_stream,
        const CreateTemporaryFileStreamCallback& callback)
    {
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE,
            base::Bind(callback, base::File::FILE_OK, base::Passed(&file_stream),
                base::RetainedRef(deletable_file_)));
    }

    base::FilePath temp_path_;
    scoped_refptr<ShareableFileReference> deletable_file_;
    // These are owned by the ResourceLoader.
    net::testing::MockFileStream* file_stream_;
    RedirectToFileResourceHandler* redirect_to_file_resource_handler_;
};

// Tests that a RedirectToFileResourceHandler works and forwards everything
// downstream.
TEST_F(ResourceLoaderRedirectToFileTest, Basic)
{
    // Run it to completion.
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();

    // Check that the handler forwarded all information to the downstream handler.
    EXPECT_EQ(
        temp_path(),
        raw_ptr_resource_handler_->resource_response()->head.download_file_path);
    EXPECT_EQ(test_redirect_url(), raw_ptr_resource_handler_->start_url());
    EXPECT_EQ(net::URLRequestStatus::SUCCESS,
        raw_ptr_resource_handler_->final_status().status());
    EXPECT_EQ(test_data().size(), static_cast<size_t>(raw_ptr_resource_handler_->total_bytes_downloaded()));

    // Check that the data was written to the file.
    std::string contents;
    ASSERT_TRUE(base::ReadFileToString(temp_path(), &contents));
    EXPECT_EQ(test_data(), contents);
}

// Tests that RedirectToFileResourceHandler handles errors in creating the
// temporary file.
TEST_F(ResourceLoaderRedirectToFileTest, CreateTemporaryError)
{
    // Swap out the create temporary function.
    redirect_to_file_resource_handler()->SetCreateTemporaryFileStreamFunctionForTesting(
        base::Bind(&CreateTemporaryError, base::File::FILE_ERROR_FAILED));

    // Run it to completion.
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();

    // To downstream, the request was canceled.
    EXPECT_EQ(net::URLRequestStatus::CANCELED,
        raw_ptr_resource_handler_->final_status().status());
    EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded());
}

// Tests that RedirectToFileResourceHandler handles synchronous write errors.
TEST_F(ResourceLoaderRedirectToFileTest, WriteError)
{
    file_stream()->set_forced_error(net::ERR_FAILED);

    // Run it to completion.
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();

    // To downstream, the request was canceled sometime after it started, but
    // before any data was written.
    EXPECT_EQ(
        temp_path(),
        raw_ptr_resource_handler_->resource_response()->head.download_file_path);
    EXPECT_EQ(test_redirect_url(), raw_ptr_resource_handler_->start_url());
    EXPECT_EQ(net::URLRequestStatus::CANCELED,
        raw_ptr_resource_handler_->final_status().status());
    EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded());
}

// Tests that RedirectToFileResourceHandler handles asynchronous write errors.
TEST_F(ResourceLoaderRedirectToFileTest, WriteErrorAsync)
{
    file_stream()->set_forced_error_async(net::ERR_FAILED);

    // Run it to completion.
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();

    // To downstream, the request was canceled sometime after it started, but
    // before any data was written.
    EXPECT_EQ(
        temp_path(),
        raw_ptr_resource_handler_->resource_response()->head.download_file_path);
    EXPECT_EQ(test_redirect_url(), raw_ptr_resource_handler_->start_url());
    EXPECT_EQ(net::URLRequestStatus::CANCELED,
        raw_ptr_resource_handler_->final_status().status());
    EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded());
}

// Tests that RedirectToFileHandler defers completion if there are outstanding
// writes and accounts for errors which occur in that time.
TEST_F(ResourceLoaderRedirectToFileTest, DeferCompletion)
{
    // Program the MockFileStream to error asynchronously, but throttle the
    // callback.
    file_stream()->set_forced_error_async(net::ERR_FAILED);
    file_stream()->ThrottleCallbacks();

    // Run it as far as it will go.
    loader_->StartRequest();
    base::RunLoop().RunUntilIdle();

    // At this point, the request should have completed.
    EXPECT_EQ(net::URLRequestStatus::SUCCESS,
        raw_ptr_to_request_->status().status());

    // However, the resource loader stack is stuck somewhere after receiving the
    // response.
    EXPECT_EQ(
        temp_path(),
        raw_ptr_resource_handler_->resource_response()->head.download_file_path);
    EXPECT_EQ(test_redirect_url(), raw_ptr_resource_handler_->start_url());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded());

    // Now, release the floodgates.
    file_stream()->ReleaseCallbacks();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();

    // Although the URLRequest was successful, the leaf handler sees a failure
    // because the write never completed.
    EXPECT_EQ(net::URLRequestStatus::CANCELED,
        raw_ptr_resource_handler_->final_status().status());
}

// Tests that a RedirectToFileResourceHandler behaves properly when the
// downstream handler defers OnWillStart.
TEST_F(ResourceLoaderRedirectToFileTest, DownstreamDeferStart)
{
    // Defer OnWillStart.
    raw_ptr_resource_handler_->set_defer_on_will_start(true);

    // Run as far as we'll go.
    loader_->StartRequest();
    raw_ptr_resource_handler_->WaitUntilDeferred();

    // The request should have stopped at OnWillStart.
    EXPECT_EQ(test_redirect_url(), raw_ptr_resource_handler_->start_url());
    EXPECT_FALSE(raw_ptr_resource_handler_->resource_response());
    EXPECT_EQ(0, raw_ptr_resource_handler_->on_response_completed_called());
    EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded());

    // Now resume the request. Now we complete.
    raw_ptr_resource_handler_->Resume();
    raw_ptr_resource_handler_->WaitUntilResponseComplete();

    // Check that the handler forwarded all information to the downstream handler.
    EXPECT_EQ(
        temp_path(),
        raw_ptr_resource_handler_->resource_response()->head.download_file_path);
    EXPECT_EQ(test_redirect_url(), raw_ptr_resource_handler_->start_url());
    EXPECT_EQ(net::URLRequestStatus::SUCCESS,
        raw_ptr_resource_handler_->final_status().status());
    EXPECT_EQ(test_data().size(), static_cast<size_t>(raw_ptr_resource_handler_->total_bytes_downloaded()));

    // Check that the data was written to the file.
    std::string contents;
    ASSERT_TRUE(base::ReadFileToString(temp_path(), &contents));
    EXPECT_EQ(test_data(), contents);
}

class EffectiveConnectionTypeResourceLoaderTest : public ResourceLoaderTest {
public:
    void VerifyEffectiveConnectionType(
        ResourceType resource_type,
        bool belongs_to_main_frame,
        net::EffectiveConnectionType set_type,
        net::EffectiveConnectionType expected_type)
    {
        network_quality_estimator()->set_effective_connection_type(set_type);

        // Start the request and wait for it to finish.
        std::unique_ptr<net::URLRequest> request(
            resource_context_.GetRequestContext()->CreateRequest(
                test_redirect_url(), net::DEFAULT_PRIORITY,
                nullptr /* delegate */));
        SetUpResourceLoader(std::move(request), resource_type,
            belongs_to_main_frame);

        // Send the request and wait until it completes.
        loader_->StartRequest();
        raw_ptr_resource_handler_->WaitUntilResponseComplete();
        ASSERT_EQ(net::URLRequestStatus::SUCCESS,
            raw_ptr_to_request_->status().status());

        EXPECT_EQ(expected_type, raw_ptr_resource_handler_->resource_response()->head.effective_connection_type);
    }
};

// Tests that the effective connection type is set on main frame requests.
TEST_F(EffectiveConnectionTypeResourceLoaderTest, Slow2G)
{
    VerifyEffectiveConnectionType(RESOURCE_TYPE_MAIN_FRAME, true,
        net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
        net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
}

// Tests that the effective connection type is set on main frame requests.
TEST_F(EffectiveConnectionTypeResourceLoaderTest, 3G)
{
    VerifyEffectiveConnectionType(RESOURCE_TYPE_MAIN_FRAME, true,
        net::EFFECTIVE_CONNECTION_TYPE_3G,
        net::EFFECTIVE_CONNECTION_TYPE_3G);
}

// Tests that the effective connection type is not set on requests that belong
// to main frame.
TEST_F(EffectiveConnectionTypeResourceLoaderTest, BelongsToMainFrame)
{
    VerifyEffectiveConnectionType(RESOURCE_TYPE_OBJECT, true,
        net::EFFECTIVE_CONNECTION_TYPE_3G,
        net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
}

// Tests that the effective connection type is not set on non-main frame
// requests.
TEST_F(EffectiveConnectionTypeResourceLoaderTest, DoesNotBelongToMainFrame)
{
    VerifyEffectiveConnectionType(RESOURCE_TYPE_OBJECT, false,
        net::EFFECTIVE_CONNECTION_TYPE_3G,
        net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
}

} // namespace content
